/*
 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.java2d;

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.nio.*;

import sun.awt.*;
import sun.awt.image.*;
import sun.java2d.loops.*;
import sun.java2d.pipe.*;
import sun.lwawt.macosx.*;

import java.lang.annotation.Native;

/*
 * This is the SurfaceData for a CGContextRef.
 */
public abstract class OSXSurfaceData extends BufImgSurfaceData {
    static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
    static final float LOWER_BND = -UPPER_BND;

    protected static CRenderer sQuartzPipe = null;
    protected static CTextPipe sCocoaTextPipe = null;
    protected static CompositeCRenderer sQuartzCompositePipe = null;

    private GraphicsConfiguration fConfig;
    private Rectangle fBounds; // bounds in user coordinates

    static {
        sQuartzPipe = new CRenderer(); // Creates the singleton quartz pipe.
    }

    // NOTE: Any subclasses must eventually call QuartzSurfaceData_InitOps in OSXSurfaceData.h
    // This sets up the native side for the SurfaceData, and is required.
    public OSXSurfaceData(SurfaceType sType, ColorModel cm) {
        this(sType, cm, null, new Rectangle());
    }

    public OSXSurfaceData(SurfaceType sType, ColorModel cm, GraphicsConfiguration config, Rectangle bounds) {
        super(sType, cm);

        this.fConfig = config;

        this.fBounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.y + bounds.height);

        this.fGraphicsStates = getBufferOfSize(kSizeOfParameters);
        this.fGraphicsStatesInt = this.fGraphicsStates.asIntBuffer();
        this.fGraphicsStatesFloat = this.fGraphicsStates.asFloatBuffer();
        this.fGraphicsStatesLong = this.fGraphicsStates.asLongBuffer();
        this.fGraphicsStatesObject = new Object[8]; // clip coordinates +
                                                    // clip types +
                                                    // texture paint image +
                                                    // stroke dash array +
                                                    // font + font paint +
                                                    // linear/radial gradient color +
                                                    // linear/radial gradient fractions

        // NOTE: All access to the DrawingQueue comes through this OSXSurfaceData instance. Therefore
        // every instance method of OSXSurfaceData that accesses the fDrawingQueue is synchronized.

        // Thread.dumpStack();
    }

    @Override
    public void validatePipe(SunGraphics2D sg2d) {

        if (sg2d.compositeState <= SunGraphics2D.COMP_ALPHA) {
            if (sCocoaTextPipe == null) {
                sCocoaTextPipe = new CTextPipe();
            }

            sg2d.imagepipe = sQuartzPipe;
            sg2d.drawpipe = sQuartzPipe;
            sg2d.fillpipe = sQuartzPipe;
            sg2d.shapepipe = sQuartzPipe;
            sg2d.textpipe = sCocoaTextPipe;
        } else {
            setPipesToQuartzComposite(sg2d);
        }
    }

    protected void setPipesToQuartzComposite(SunGraphics2D sg2d) {
        if (sQuartzCompositePipe == null) {
            sQuartzCompositePipe = new CompositeCRenderer();
        }

        if (sCocoaTextPipe == null) {
            sCocoaTextPipe = new CTextPipe();
        }

        sg2d.imagepipe = sQuartzCompositePipe;
        sg2d.drawpipe = sQuartzCompositePipe;
        sg2d.fillpipe = sQuartzCompositePipe;
        sg2d.shapepipe = sQuartzCompositePipe;
        sg2d.textpipe = sCocoaTextPipe;
    }

    @Override
    public Rectangle getBounds() {
        // gznote: always return a copy, not the rect itself and translate into device space
        return new Rectangle(fBounds.x, fBounds.y, fBounds.width, fBounds.height - fBounds.y);
    }

    @Override
    public GraphicsConfiguration getDeviceConfiguration() {
        return fConfig;
    }

    protected void setBounds(int x, int y, int w, int h) {
        fBounds.setBounds(x, y, w, y + h);
    }

    // START compositing support API
    public abstract BufferedImage copyArea(SunGraphics2D sg2d, int x, int y, int w, int h, BufferedImage image);

    public abstract boolean xorSurfacePixels(SunGraphics2D sg2d, BufferedImage srcPixels, int x, int y, int w, int h, int colorXOR);

    GraphicsConfiguration sDefaultGraphicsConfiguration = null;

    protected BufferedImage getCompositingImage(int w, int h) {
        if (sDefaultGraphicsConfiguration == null) {
            sDefaultGraphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        }

        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
        // clear the image.
        clearRect(img, w, h);
        return img;
    }

    protected BufferedImage getCompositingImageSame(BufferedImage img, int w, int h) {
        if ((img == null) || (img.getWidth() != w) || (img.getHeight() != h)) {
            img = getCompositingImage(w, h);
        }
        return img;
    }

    BufferedImage sSrcComposite = null;

    public BufferedImage getCompositingSrcImage(int w, int h) {
        // <rdar://problem/3720263>. Changed from getCompositingImageBiggerOrSame() to
        // getCompositingImageSame(). (vm)
        BufferedImage bim = getCompositingImageSame(sSrcComposite, w, h);
        sSrcComposite = bim;
        return bim;
    }

    BufferedImage sDstInComposite = null;

    public BufferedImage getCompositingDstInImage(int w, int h) {
        BufferedImage bim = getCompositingImageSame(sDstInComposite, w, h);
        sDstInComposite = bim;
        return bim;
    }

    BufferedImage sDstOutComposite = null;

    public BufferedImage getCompositingDstOutImage(int w, int h) {
        BufferedImage bim = getCompositingImageSame(sDstOutComposite, w, h);
        sDstOutComposite = bim;
        return bim;
    }

    public void clearRect(BufferedImage bim, int w, int h) {
        Graphics2D g = bim.createGraphics();
        g.setComposite(AlphaComposite.Clear);
        g.fillRect(0, 0, w, h);
        g.dispose();
    }

    // END compositing support API

     // graphics primitives drawing implementation:

    // certain primitives don't care about all the states (ex. drawing an image needs not involve setting current paint)
    @Native static final int kPrimitive = 0;
    @Native static final int kImage = 1;
    @Native static final int kText = 2;
    @Native static final int kCopyArea = 3;
    @Native static final int kExternal = 4;

    @Native static final int kLine = 5; // belongs to kPrimitive
    @Native static final int kRect = 6; // belongs to kPrimitive
    @Native static final int kRoundRect = 7; // belongs to kPrimitive
    @Native static final int kOval = 8; // belongs to kPrimitive
    @Native static final int kArc = 9; // belongs to kPrimitive
    @Native static final int kPolygon = 10; // belongs to kPrimitive
    @Native static final int kShape = 11; // belongs to kPrimitive
    // static final int kImage = 12; // belongs to kImage
    @Native static final int kString = 13; // belongs to kText
    @Native static final int kGlyphs = 14; // belongs to kText
    @Native static final int kUnicodes = 15; // belongs to kText
    // static final int kCopyArea = 16; // belongs to kCopyArea
    // static final int kExternal = 17; // belongs to kExternal

    @Native static final int kCommonParameterCount = 1 + 1 + 4 + 4; // type + change flags + color info (type(1) align(1) and
                                                            // value(2)) + parameters ((x1, y1, x2, y2) OR (x, y, w, h))
    @Native static final int kLineParametersCount = kCommonParameterCount; // kCommonParameterCount
    @Native static final int kRectParametersCount = kCommonParameterCount + 1; // kCommonParameterCount + isfill
    @Native static final int kRoundRectParametersCount = kCommonParameterCount + 2 + 1; // kCommonParameterCount + arcW + arcH +
                                                                                // isfill
    @Native static final int kOvalParametersCount = kCommonParameterCount + 1; // kCommonParameterCount + isfill
    @Native static final int kArcParametersCount = kCommonParameterCount + 2 + 1 + 1;// kCommonParameterCount + startAngle +
                                                                             // arcAngle + isfill + type
    @Native static final int kPolygonParametersCount = 0; // not supported
    @Native static final int kShapeParametersCount = 0; // not supported
    @Native static final int kImageParametersCount = kCommonParameterCount + 2 + 2 + 4 + 4; // flip horz vert + w&h + src + dst
    @Native static final int kStringParametersCount = 0; // not supported
    @Native static final int kGlyphsParametersCount = 0; // not supported
    @Native static final int kUnicodesParametersCount = 0; // not supported
    @Native static final int kPixelParametersCount = 0; // not supported
    @Native static final int kExternalParametersCount = 0; // not supported

    // for intParameters
    // states info
    @Native static final int kChangeFlagIndex = 0; // kBoundsChangedBit | .. | kFontChangedBit
    // bounds info
    @Native static final int kBoundsXIndex = 1;
    @Native static final int kBoundsYIndex = 2;
    @Native static final int kBoundsWidthIndex = 3;
    @Native static final int kBoundsHeightIndex = 4;
    // clip info
    @Native static final int kClipStateIndex = 5;
    @Native static final int kClipNumTypesIndex = 6;
    @Native static final int kClipNumCoordsIndex = 7;
    @Native static final int kClipWindingRuleIndex = 8;
    @Native static final int kClipXIndex = 9;
    @Native static final int kClipYIndex = 10;
    @Native static final int kClipWidthIndex = 11;
    @Native static final int kClipHeightIndex = 12;
    // ctm info
    @Native static final int kCTMaIndex = 13;
    @Native static final int kCTMbIndex = 14;
    @Native static final int kCTMcIndex = 15;
    @Native static final int kCTMdIndex = 16;
    @Native static final int kCTMtxIndex = 17;
    @Native static final int kCTMtyIndex = 18;
    // color info
    @Native static final int kColorStateIndex = 19; // kColorSimple or kColorGradient or kColorTexture
    @Native static final int kColorRGBValueIndex = 20; // if kColorSimple
    @Native static final int kColorIndexValueIndex = 21; // if kColorSystem
    @Native static final int kColorPointerIndex = 22; //
    @Native static final int kColorPointerIndex2 = 23; //
    @Native static final int kColorRGBValue1Index = 24; // if kColorGradient
    @Native static final int kColorWidthIndex = 25; // if kColorTexture
    @Native static final int kColorRGBValue2Index = 26; // if kColorGradient
    @Native static final int kColorHeightIndex = 27; // if kColorTexture
    @Native static final int kColorIsCyclicIndex = 28; // if kColorGradient (kColorNonCyclic or kColorCyclic)
    @Native static final int kColorx1Index = 29;
    @Native static final int kColortxIndex = 30;
    @Native static final int kColory1Index = 31;
    @Native static final int kColortyIndex = 32;
    @Native static final int kColorx2Index = 33;
    @Native static final int kColorsxIndex = 34;
    @Native static final int kColory2Index = 35;
    @Native static final int kColorsyIndex = 36;
    // composite info
    @Native static final int kCompositeRuleIndex = 37; // kCGCompositeClear or ... or kCGCompositeXor
    @Native static final int kCompositeValueIndex = 38;
    // stroke info
    @Native static final int kStrokeJoinIndex = 39; // see BasicStroke.java
    @Native static final int kStrokeCapIndex = 40; // see BasicStroke.java
    @Native static final int kStrokeWidthIndex = 41;
    @Native static final int kStrokeDashPhaseIndex = 42;
    @Native static final int kStrokeLimitIndex = 43;
    // hints info
    @Native static final int kHintsAntialiasIndex = 44;
    @Native static final int kHintsTextAntialiasIndex = 45;
    @Native static final int kHintsFractionalMetricsIndex = 46;
    @Native static final int kHintsRenderingIndex = 47;
    @Native static final int kHintsInterpolationIndex = 48;
    //gradient info
    @Native static final int kRadiusIndex = 49;

    @Native static final int kSizeOfParameters = kRadiusIndex + 1;

    // for objectParameters
    @Native static final int kClipCoordinatesIndex = 0;
    @Native static final int kClipTypesIndex = 1;
    @Native static final int kTextureImageIndex = 2;
    @Native static final int kStrokeDashArrayIndex = 3;
    @Native static final int kFontIndex = 4;
    @Native static final int kFontPaintIndex = 5;
    @Native static final int kColorArrayIndex = 6;
    @Native static final int kFractionsArrayIndex = 7;

    // possible state changes
    @Native static final int kBoundsChangedBit = 1 << 0;
    @Native static final int kBoundsNotChangedBit = ~kBoundsChangedBit;
    @Native static final int kClipChangedBit = 1 << 1;
    @Native static final int kClipNotChangedBit = ~kClipChangedBit;
    @Native static final int kCTMChangedBit = 1 << 2;
    @Native static final int kCTMNotChangedBit = ~kCTMChangedBit;
    @Native static final int kColorChangedBit = 1 << 3;
    @Native static final int kColorNotChangedBit = ~kColorChangedBit;
    @Native static final int kCompositeChangedBit = 1 << 4;
    @Native static final int kCompositeNotChangedBit = ~kCompositeChangedBit;
    @Native static final int kStrokeChangedBit = 1 << 5;
    @Native static final int kStrokeNotChangedBit = ~kStrokeChangedBit;
    @Native static final int kHintsChangedBit = 1 << 6;
    @Native static final int kHintsNotChangedBit = ~kHintsChangedBit;
    @Native static final int kFontChangedBit = 1 << 7;
    @Native static final int kFontNotChangedBit = ~kFontChangedBit;
    @Native static final int kEverythingChangedFlag = 0xffffffff;

    // possible color states
    @Native static final int kColorSimple = 0;
    @Native static final int kColorSystem = 1;
    @Native static final int kColorGradient = 2;
    @Native static final int kColorTexture = 3;
    @Native static final int kColorLinearGradient = 4;
    @Native static final int kColorRadialGradient = 5;

    // possible gradient color states
    @Native static final int kColorNonCyclic = 0;
    @Native static final int kColorCyclic = 1;

    // possible clip states
    @Native static final int kClipRect = 0;
    @Native static final int kClipShape = 1;

    static int getRendererTypeForPrimitive(int primitiveType) {
        switch (primitiveType) {
            case kImage:
                return kImage;
            case kCopyArea:
                return kCopyArea;
            case kExternal:
                return kExternal;
            case kString:
            case kGlyphs:
            case kUnicodes:
                return kText;
            default:
                return kPrimitive;
        }
    }

    int fChangeFlag;
    protected ByteBuffer fGraphicsStates = null;
    IntBuffer fGraphicsStatesInt = null;
    FloatBuffer fGraphicsStatesFloat = null;
    LongBuffer fGraphicsStatesLong = null;
    protected Object[] fGraphicsStatesObject = null;

    Rectangle userBounds = new Rectangle();
    float lastUserX = 0;
    float lastUserY = 0;
    float lastUserW = 0;
    float lastUserH = 0;

    void setUserBounds(SunGraphics2D sg2d, int x, int y, int width, int height) {
        if ((lastUserX != x) || (lastUserY != y) || (lastUserW != width) || (lastUserH != height)) {
            lastUserX = x;
            lastUserY = y;
            lastUserW = width;
            lastUserH = height;

            this.fGraphicsStatesInt.put(kBoundsXIndex, x);
            this.fGraphicsStatesInt.put(kBoundsYIndex, y);
            this.fGraphicsStatesInt.put(kBoundsWidthIndex, width);
            this.fGraphicsStatesInt.put(kBoundsHeightIndex, height);

            userBounds.setBounds(x, y, width, height);

            this.fChangeFlag = (this.fChangeFlag | kBoundsChangedBit);
        } else {
            this.fChangeFlag = (this.fChangeFlag & kBoundsNotChangedBit);
        }
    }

    static ByteBuffer getBufferOfSize(int size) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(size * 4);
        buffer.order(ByteOrder.nativeOrder());
        return buffer;
    }

    FloatBuffer clipCoordinatesArray = null;
    IntBuffer clipTypesArray = null;
    Shape lastClipShape = null;
    float lastClipX = 0;
    float lastClipY = 0;
    float lastClipW = 0;
    float lastClipH = 0;

    void setupClip(SunGraphics2D sg2d) {
        switch (sg2d.clipState) {
            case SunGraphics2D.CLIP_DEVICE:
            case SunGraphics2D.CLIP_RECTANGULAR: {
                Region clip = sg2d.getCompClip();
                float x = clip.getLoX();
                float y = clip.getLoY();
                float w = clip.getWidth();
                float h = clip.getHeight();
                if ((this.fGraphicsStatesInt.get(kClipStateIndex) != kClipRect) ||
                        (x != lastClipX) ||
                            (y != lastClipY) ||
                                (w != lastClipW) ||
                                    (h != lastClipH)) {
                    this.fGraphicsStatesFloat.put(kClipXIndex, x);
                    this.fGraphicsStatesFloat.put(kClipYIndex, y);
                    this.fGraphicsStatesFloat.put(kClipWidthIndex, w);
                    this.fGraphicsStatesFloat.put(kClipHeightIndex, h);

                    lastClipX = x;
                    lastClipY = y;
                    lastClipW = w;
                    lastClipH = h;

                    this.fChangeFlag = (this.fChangeFlag | kClipChangedBit);
                } else {
                    this.fChangeFlag = (this.fChangeFlag & kClipNotChangedBit);
                }
                this.fGraphicsStatesInt.put(kClipStateIndex, kClipRect);
                break;
            }
            case SunGraphics2D.CLIP_SHAPE: {
                // if (lastClipShape != sg2d.usrClip) shapes are mutable!, and doing "equals" traverses all
                // the coordinates, so we might as well do all of it anyhow
                lastClipShape = sg2d.usrClip;

                GeneralPath gp = null;

                if (sg2d.usrClip instanceof GeneralPath) {
                    gp = (GeneralPath) sg2d.usrClip;
                } else {
                    gp = new GeneralPath(sg2d.usrClip);
                }

                int shapeLength = getPathLength(gp);

                if ((clipCoordinatesArray == null) || (clipCoordinatesArray.capacity() < (shapeLength * 6))) {
                    clipCoordinatesArray = getBufferOfSize(shapeLength * 6).asFloatBuffer(); // segment can have a
                                                                                             // max of 6 coordinates
                }
                if ((clipTypesArray == null) || (clipTypesArray.capacity() < shapeLength)) {
                    clipTypesArray = getBufferOfSize(shapeLength).asIntBuffer();
                }

                int windingRule = getPathCoordinates(gp, clipCoordinatesArray, clipTypesArray);

                this.fGraphicsStatesInt.put(kClipNumTypesIndex, clipTypesArray.position());
                this.fGraphicsStatesInt.put(kClipNumCoordsIndex, clipCoordinatesArray.position());
                this.fGraphicsStatesInt.put(kClipWindingRuleIndex, windingRule);
                this.fGraphicsStatesObject[kClipTypesIndex] = clipTypesArray;
                this.fGraphicsStatesObject[kClipCoordinatesIndex] = clipCoordinatesArray;

                this.fChangeFlag = (this.fChangeFlag | kClipChangedBit);
                this.fGraphicsStatesInt.put(kClipStateIndex, kClipShape);
                break;
            }
        }

    }

    final double[] lastCTM = new double[6];
    float lastCTMa = 0;
    float lastCTMb = 0;
    float lastCTMc = 0;
    float lastCTMd = 0;
    float lastCTMtx = 0;
    float lastCTMty = 0;

    void setupTransform(SunGraphics2D sg2d) {
        sg2d.transform.getMatrix(lastCTM);

        float a = (float) lastCTM[0];
        float b = (float) lastCTM[1];
        float c = (float) lastCTM[2];
        float d = (float) lastCTM[3];
        float tx = (float) lastCTM[4];
        float ty = (float) lastCTM[5];
        if (tx != lastCTMtx ||
                ty != lastCTMty ||
                    a != lastCTMa ||
                        b != lastCTMb ||
                            c != lastCTMc ||
                                d != lastCTMd) {
            this.fGraphicsStatesFloat.put(kCTMaIndex, a);
            this.fGraphicsStatesFloat.put(kCTMbIndex, b);
            this.fGraphicsStatesFloat.put(kCTMcIndex, c);
            this.fGraphicsStatesFloat.put(kCTMdIndex, d);
            this.fGraphicsStatesFloat.put(kCTMtxIndex, tx);
            this.fGraphicsStatesFloat.put(kCTMtyIndex, ty);

            lastCTMa = a;
            lastCTMb = b;
            lastCTMc = c;
            lastCTMd = d;
            lastCTMtx = tx;
            lastCTMty = ty;

            this.fChangeFlag = (this.fChangeFlag | kCTMChangedBit);
        } else {
            this.fChangeFlag = (this.fChangeFlag & kCTMNotChangedBit);
        }
    }

    static AffineTransform sIdentityMatrix = new AffineTransform();
    Paint lastPaint = null;
    long lastPaintPtr = 0;
    int lastPaintRGB = 0;
    int lastPaintIndex = 0;
    BufferedImage texturePaintImage = null;

    void setGradientViaRasterPath(SunGraphics2D sg2d) {
        if ((this.fGraphicsStatesInt.get(kColorStateIndex) != kColorTexture) || (lastPaint != sg2d.paint) || ((this.fChangeFlag & kBoundsChangedBit) != 0)) {
            PaintContext context = sg2d.paint.createContext(sg2d.getDeviceColorModel(), userBounds, userBounds, sIdentityMatrix, sg2d.getRenderingHints());
            WritableRaster raster = (WritableRaster) (context.getRaster(userBounds.x, userBounds.y, userBounds.width, userBounds.height));
            ColorModel cm = context.getColorModel();
            texturePaintImage = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);

            this.fGraphicsStatesInt.put(kColorStateIndex, kColorTexture);
            this.fGraphicsStatesInt.put(kColorWidthIndex, texturePaintImage.getWidth());
            this.fGraphicsStatesInt.put(kColorHeightIndex, texturePaintImage.getHeight());
            this.fGraphicsStatesFloat.put(kColortxIndex, (float) userBounds.getX());
            this.fGraphicsStatesFloat.put(kColortyIndex, (float) userBounds.getY());
            this.fGraphicsStatesFloat.put(kColorsxIndex, 1.0f);
            this.fGraphicsStatesFloat.put(kColorsyIndex, 1.0f);
            this.fGraphicsStatesObject[kTextureImageIndex] = OSXOffScreenSurfaceData.createNewSurface(texturePaintImage);

            this.fChangeFlag = (this.fChangeFlag | kColorChangedBit);
        } else {
            this.fChangeFlag = (this.fChangeFlag & kColorNotChangedBit);
        }
    }

    void setupPaint(SunGraphics2D sg2d, int x, int y, int w, int h) {
        if (sg2d.paint instanceof SystemColor) {
            SystemColor color = (SystemColor) sg2d.paint;
            int index = color.hashCode(); // depends on Color.java hashCode implementation! (returns "value" of color)
            if ((this.fGraphicsStatesInt.get(kColorStateIndex) != kColorSystem) || (index != this.lastPaintIndex)) {
                this.lastPaintIndex = index;

                this.fGraphicsStatesInt.put(kColorStateIndex, kColorSystem);
                this.fGraphicsStatesInt.put(kColorIndexValueIndex, index);

                this.fChangeFlag = (this.fChangeFlag | kColorChangedBit);
            } else {
                this.fChangeFlag = (this.fChangeFlag & kColorNotChangedBit);
            }
        } else if (sg2d.paint instanceof Color) {
            Color color = (Color) sg2d.paint;
            int rgb = color.getRGB();
            if ((this.fGraphicsStatesInt.get(kColorStateIndex) != kColorSimple) || (rgb != this.lastPaintRGB)) {
                this.lastPaintRGB = rgb;

                this.fGraphicsStatesInt.put(kColorStateIndex, kColorSimple);
                this.fGraphicsStatesInt.put(kColorRGBValueIndex, rgb);

                this.fChangeFlag = (this.fChangeFlag | kColorChangedBit);
            } else {
                this.fChangeFlag = (this.fChangeFlag & kColorNotChangedBit);
            }
        } else if (sg2d.paint instanceof GradientPaint) {
            if ((this.fGraphicsStatesInt.get(kColorStateIndex) != kColorGradient) || (lastPaint != sg2d.paint)) {
                GradientPaint color = (GradientPaint) sg2d.paint;
                this.fGraphicsStatesInt.put(kColorStateIndex, kColorGradient);
                this.fGraphicsStatesInt.put(kColorRGBValue1Index, color.getColor1().getRGB());
                this.fGraphicsStatesInt.put(kColorRGBValue2Index, color.getColor2().getRGB());
                this.fGraphicsStatesInt.put(kColorIsCyclicIndex, (color.isCyclic()) ? kColorCyclic : kColorNonCyclic);
                Point2D p = color.getPoint1();
                this.fGraphicsStatesFloat.put(kColorx1Index, (float) p.getX());
                this.fGraphicsStatesFloat.put(kColory1Index, (float) p.getY());
                p = color.getPoint2();
                this.fGraphicsStatesFloat.put(kColorx2Index, (float) p.getX());
                this.fGraphicsStatesFloat.put(kColory2Index, (float) p.getY());

                this.fChangeFlag = (this.fChangeFlag | kColorChangedBit);
            } else {
                this.fChangeFlag = (this.fChangeFlag & kColorNotChangedBit);
            }
        } else if (sg2d.paint instanceof LinearGradientPaint) {
            LinearGradientPaint color = (LinearGradientPaint) sg2d.paint;
            if (color.getCycleMethod() == LinearGradientPaint.CycleMethod.NO_CYCLE) {
                if ((this.fGraphicsStatesInt.get(kColorStateIndex) != kColorLinearGradient) || (lastPaint != sg2d.paint)) {

                    this.fGraphicsStatesInt.put(kColorStateIndex, kColorLinearGradient);
                    int numColor = color.getColors().length;
                    int[] colorArray = new int[numColor];
                    for (int i = 0; i < numColor; i++) {
                        colorArray[i] = color.getColors()[i].getRGB();
                    }
                    this.fGraphicsStatesObject[kColorArrayIndex] = colorArray;

                    int numFractions = color.getFractions().length;
                    float[] fractionArray = new float[numFractions];
                    for (int i = 0; i < numFractions; i++) {
                        fractionArray[i] = color.getFractions()[i];
                    }
                    this.fGraphicsStatesObject[kFractionsArrayIndex] = color.getFractions();

                    Point2D p = color.getStartPoint();
                    this.fGraphicsStatesFloat.put(kColorx1Index, (float) p.getX());
                    this.fGraphicsStatesFloat.put(kColory1Index, (float) p.getY());
                    p = color.getEndPoint();
                    this.fGraphicsStatesFloat.put(kColorx2Index, (float) p.getX());
                    this.fGraphicsStatesFloat.put(kColory2Index, (float) p.getY());

                    this.fChangeFlag = (this.fChangeFlag | kColorChangedBit);
                } else {
                    this.fChangeFlag = (this.fChangeFlag & kColorNotChangedBit);
                }
            } else {
                setGradientViaRasterPath(sg2d);
            }
        } else if (sg2d.paint instanceof RadialGradientPaint) {
            RadialGradientPaint color = (RadialGradientPaint) sg2d.paint;
            if (color.getCycleMethod() == RadialGradientPaint.CycleMethod.NO_CYCLE) {
                if ((this.fGraphicsStatesInt.get(kColorStateIndex) != kColorRadialGradient) || (lastPaint != sg2d.paint)) {

                    this.fGraphicsStatesInt.put(kColorStateIndex, kColorRadialGradient);
                    int numColor = color.getColors().length;
                    int[] colorArray = new int[numColor];
                    for (int i = 0; i < numColor; i++) {
                        colorArray[i] = color.getColors()[i].getRGB();
                    }
                    this.fGraphicsStatesObject[kColorArrayIndex] = colorArray;

                    int numStops = color.getFractions().length;
                    float[] stopsArray = new float[numStops];
                    for (int i = 0; i < numStops; i++) {
                        stopsArray[i] = color.getFractions()[i];
                    }
                    this.fGraphicsStatesObject[kFractionsArrayIndex] = color.getFractions();

                    Point2D p = color.getFocusPoint();
                    this.fGraphicsStatesFloat.put(kColorx1Index, (float) p.getX());
                    this.fGraphicsStatesFloat.put(kColory1Index, (float) p.getY());
                    p = color.getCenterPoint();
                    this.fGraphicsStatesFloat.put(kColorx2Index, (float) p.getX());
                    this.fGraphicsStatesFloat.put(kColory2Index, (float) p.getY());
                    this.fGraphicsStatesFloat.put(kRadiusIndex,     color.getRadius());

                    this.fChangeFlag = (this.fChangeFlag | kColorChangedBit);
                } else {
                    this.fChangeFlag = (this.fChangeFlag & kColorNotChangedBit);
                }
            } else {
                setGradientViaRasterPath(sg2d);
            }
        } else if (sg2d.paint instanceof TexturePaint) {
            if ((this.fGraphicsStatesInt.get(kColorStateIndex) != kColorTexture) || (lastPaint != sg2d.paint)) {
                TexturePaint color = (TexturePaint) sg2d.paint;
                this.fGraphicsStatesInt.put(kColorStateIndex, kColorTexture);
                texturePaintImage = color.getImage();
                SurfaceData textureSurfaceData = OSXOffScreenSurfaceData.createNewSurface(texturePaintImage);
                this.fGraphicsStatesInt.put(kColorWidthIndex, texturePaintImage.getWidth());
                this.fGraphicsStatesInt.put(kColorHeightIndex, texturePaintImage.getHeight());
                Rectangle2D anchor = color.getAnchorRect();
                this.fGraphicsStatesFloat.put(kColortxIndex, (float) anchor.getX());
                this.fGraphicsStatesFloat.put(kColortyIndex, (float) anchor.getY());
                this.fGraphicsStatesFloat.put(kColorsxIndex, (float) (anchor.getWidth() / texturePaintImage.getWidth()));
                this.fGraphicsStatesFloat.put(kColorsyIndex, (float) (anchor.getHeight() / texturePaintImage.getHeight()));
                this.fGraphicsStatesObject[kTextureImageIndex] = textureSurfaceData;

                this.fChangeFlag = (this.fChangeFlag | kColorChangedBit);
            } else {
                this.fChangeFlag = (this.fChangeFlag & kColorNotChangedBit);
            }
        } else {
            setGradientViaRasterPath(sg2d);
        }
        lastPaint = sg2d.paint;
    }

    Composite lastComposite;
    int lastCompositeAlphaRule = 0;
    float lastCompositeAlphaValue = 0;

    void setupComposite(SunGraphics2D sg2d) {
        Composite composite = sg2d.composite;

        if (lastComposite != composite) {
            lastComposite = composite;

            // For composite state COMP_ISCOPY, COMP_XOR or COMP_CUSTOM set alpha compositor to COPY:
            int alphaRule = AlphaComposite.SRC_OVER;
            float alphaValue = 1.0f;

            // For composite state COMP_ISCOPY composite could be null. If it's not (or composite state == COMP_ALPHA)
            // get alpha compositor's values:
            if ((sg2d.compositeState <= SunGraphics2D.COMP_ALPHA) && (composite != null)) {
                AlphaComposite alphaComposite = (AlphaComposite) composite;
                alphaRule = alphaComposite.getRule();
                alphaValue = alphaComposite.getAlpha();
            }

            // 2-17-03 VL: [Radar 3174922]
            // For COMP_XOR and COMP_CUSTOM compositing modes we should be setting alphaRule = AlphaComposite.SRC
            // which should map to kCGCompositeCopy.

            if ((lastCompositeAlphaRule != alphaRule) || (lastCompositeAlphaValue != alphaValue)) {
                this.fGraphicsStatesInt.put(kCompositeRuleIndex, alphaRule);
                this.fGraphicsStatesFloat.put(kCompositeValueIndex, alphaValue);

                lastCompositeAlphaRule = alphaRule;
                lastCompositeAlphaValue = alphaValue;

                this.fChangeFlag = (this.fChangeFlag | kCompositeChangedBit);
            } else {
                this.fChangeFlag = (this.fChangeFlag & kCompositeNotChangedBit);
            }
        } else {
            this.fChangeFlag = (this.fChangeFlag & kCompositeNotChangedBit);
        }
    }

    BasicStroke lastStroke = null;
    static BasicStroke defaultBasicStroke = new BasicStroke();

    void setupStroke(SunGraphics2D sg2d) {
        BasicStroke stroke = defaultBasicStroke;

        if (sg2d.stroke instanceof BasicStroke) {
            stroke = (BasicStroke) sg2d.stroke;
        }

        if (lastStroke != stroke) {
            this.fGraphicsStatesObject[kStrokeDashArrayIndex] = stroke.getDashArray();
            this.fGraphicsStatesFloat.put(kStrokeDashPhaseIndex, stroke.getDashPhase());
            this.fGraphicsStatesInt.put(kStrokeCapIndex, stroke.getEndCap());
            this.fGraphicsStatesInt.put(kStrokeJoinIndex, stroke.getLineJoin());
            this.fGraphicsStatesFloat.put(kStrokeWidthIndex, stroke.getLineWidth());
            this.fGraphicsStatesFloat.put(kStrokeLimitIndex, stroke.getMiterLimit());

            this.fChangeFlag = (this.fChangeFlag | kStrokeChangedBit);

            lastStroke = stroke;
        } else {
            this.fChangeFlag = (this.fChangeFlag & kStrokeNotChangedBit);
        }
    }

    Font lastFont;

    void setupFont(Font font, Paint paint) {
        if (font == null) { return; }

        // We have to setup the kFontPaintIndex if we have changed the color so we added the last
        // test to see if the color has changed - needed for complex strings
        // see Radar 3368674
        if ((font != lastFont) || ((this.fChangeFlag & kColorChangedBit) != 0)) {
            this.fGraphicsStatesObject[kFontIndex] = font;
            this.fGraphicsStatesObject[kFontPaintIndex] = paint;

            this.fChangeFlag = (this.fChangeFlag | kFontChangedBit);

            lastFont = font;
        } else {
            this.fChangeFlag = (this.fChangeFlag & kFontNotChangedBit);
        }
    }

    void setupRenderingHints(SunGraphics2D sg2d) {
        boolean hintsChanged = false;

        // Significant for draw, fill, text, and image ops:
        int antialiasHint = sg2d.antialiasHint;
        if (this.fGraphicsStatesInt.get(kHintsAntialiasIndex) != antialiasHint) {
            this.fGraphicsStatesInt.put(kHintsAntialiasIndex, antialiasHint);
            hintsChanged = true;
        }

        // Significant only for text ops:
        int textAntialiasHint = sg2d.textAntialiasHint;
        if (this.fGraphicsStatesInt.get(kHintsTextAntialiasIndex) != textAntialiasHint) {
            this.fGraphicsStatesInt.put(kHintsTextAntialiasIndex, textAntialiasHint);
            hintsChanged = true;
        }

        // Significant only for text ops:
        int fractionalMetricsHint = sg2d.fractionalMetricsHint;
        if (this.fGraphicsStatesInt.get(kHintsFractionalMetricsIndex) != fractionalMetricsHint) {
            this.fGraphicsStatesInt.put(kHintsFractionalMetricsIndex, fractionalMetricsHint);
            hintsChanged = true;
        }

        // Significant only for image ops:
        int renderHint = sg2d.renderHint;
        if (this.fGraphicsStatesInt.get(kHintsRenderingIndex) != renderHint) {
            this.fGraphicsStatesInt.put(kHintsRenderingIndex, renderHint);
            hintsChanged = true;
        }

        // Significant only for image ops:
        Object hintValue = sg2d.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
        int interpolationHint = (hintValue != null ? ((SunHints.Value) hintValue).getIndex() : -1);
        if (this.fGraphicsStatesInt.get(kHintsInterpolationIndex) != interpolationHint) {
            this.fGraphicsStatesInt.put(kHintsInterpolationIndex, interpolationHint);
            hintsChanged = true;
        }

        if (hintsChanged) {
            this.fChangeFlag = (this.fChangeFlag | kHintsChangedBit);
        } else {
            this.fChangeFlag = (this.fChangeFlag & kHintsNotChangedBit);
        }
    }

    SunGraphics2D sg2dCurrent = null;
    Thread threadCurrent = null;

    void setupGraphicsState(SunGraphics2D sg2d, int primitiveType) {
        setupGraphicsState(sg2d, primitiveType, sg2d.font, 0, 0, fBounds.width, fBounds.height); // deviceBounds into userBounds
    }

    void setupGraphicsState(SunGraphics2D sg2d, int primitiveType, int x, int y, int w, int h) {
        setupGraphicsState(sg2d, primitiveType, sg2d.font, x, y, w, h);
    }

    // the method below is overridden by CPeerSurface to check the last peer used to draw
    // if the peer changed we finish lazy drawing
    void setupGraphicsState(SunGraphics2D sg2d, int primitiveType, Font font, int x, int y, int w, int h) {
        this.fChangeFlag = 0;

        setUserBounds(sg2d, x, y, w, h);

        Thread thread = Thread.currentThread();
        if ((this.sg2dCurrent != sg2d) || (this.threadCurrent != thread)) {
            this.sg2dCurrent = sg2d;
            this.threadCurrent = thread;

            setupClip(sg2d);
            setupTransform(sg2d);
            setupPaint(sg2d, x, y, w, h);
            setupComposite(sg2d);
            setupStroke(sg2d);
            setupFont(font, sg2d.paint);
            setupRenderingHints(sg2d);

            this.fChangeFlag = kEverythingChangedFlag;
        } else {
            int rendererType = getRendererTypeForPrimitive(primitiveType);

            setupClip(sg2d);
            setupTransform(sg2d);

            if (rendererType != kCopyArea) {
                setupComposite(sg2d);
                setupRenderingHints(sg2d);

                if ((rendererType != kImage)) {
                    setupPaint(sg2d, x, y, w, h);
                    setupStroke(sg2d);
                }
                if (rendererType != kPrimitive) {
                    setupFont(font, sg2d.paint);
                }

            }
        }

        this.fGraphicsStatesInt.put(kChangeFlagIndex, this.fChangeFlag);
    }

    boolean isCustomPaint(SunGraphics2D sg2d) {
        if ((sg2d.paint instanceof Color) || (sg2d.paint instanceof SystemColor) || (sg2d.paint instanceof GradientPaint) || (sg2d.paint instanceof TexturePaint)) { return false; }

        return true;
    }

    final float[] segmentCoordinatesArray = new float[6];

    int getPathLength(GeneralPath gp) {
        int length = 0;

        PathIterator pi = gp.getPathIterator(null);
        while (pi.isDone() == false) {
            pi.next();
            length++;
        }

        return length;
    }

    int getPathCoordinates(GeneralPath gp, FloatBuffer coordinates, IntBuffer types) {
        // System.err.println("getPathCoordinates");
        boolean skip = false;

        coordinates.clear();
        types.clear();

        int type;

        PathIterator pi = gp.getPathIterator(null);
        while (pi.isDone() == false) {
            skip = false;
            type = pi.currentSegment(segmentCoordinatesArray);

            switch (type) {
                case PathIterator.SEG_MOVETO:
                    // System.err.println(" SEG_MOVETO ("+segmentCoordinatesArray[0]+", "+segmentCoordinatesArray[1]+")");
                    if (segmentCoordinatesArray[0] < UPPER_BND && segmentCoordinatesArray[0] > LOWER_BND &&
                            segmentCoordinatesArray[1] < UPPER_BND && segmentCoordinatesArray[1] > LOWER_BND) {
                        coordinates.put(segmentCoordinatesArray[0]);
                        coordinates.put(segmentCoordinatesArray[1]);
                    } else {
                        skip = true;
                    }
                    break;
                case PathIterator.SEG_LINETO:
                    // System.err.println(" SEG_LINETO ("+segmentCoordinatesArray[0]+", "+segmentCoordinatesArray[1]+")");
                    if (segmentCoordinatesArray[0] < UPPER_BND && segmentCoordinatesArray[0] > LOWER_BND &&
                            segmentCoordinatesArray[1] < UPPER_BND && segmentCoordinatesArray[1] > LOWER_BND) {
                        coordinates.put(segmentCoordinatesArray[0]);
                        coordinates.put(segmentCoordinatesArray[1]);
                    } else {
                        skip = true;
                    }
                    break;
                case PathIterator.SEG_QUADTO:
                    // System.err.println(" SEG_QUADTO ("+segmentCoordinatesArray[0]+", "+segmentCoordinatesArray[1]+"), ("+segmentCoordinatesArray[2]+", "+segmentCoordinatesArray[3]+")");
                    if (segmentCoordinatesArray[0] < UPPER_BND && segmentCoordinatesArray[0] > LOWER_BND &&
                            segmentCoordinatesArray[1] < UPPER_BND && segmentCoordinatesArray[1] > LOWER_BND &&
                            segmentCoordinatesArray[2] < UPPER_BND && segmentCoordinatesArray[2] > LOWER_BND &&
                            segmentCoordinatesArray[3] < UPPER_BND && segmentCoordinatesArray[3] > LOWER_BND) {
                        coordinates.put(segmentCoordinatesArray[0]);
                        coordinates.put(segmentCoordinatesArray[1]);
                        coordinates.put(segmentCoordinatesArray[2]);
                        coordinates.put(segmentCoordinatesArray[3]);
                    } else {
                        skip = true;
                    }
                    break;
                case PathIterator.SEG_CUBICTO:
                    // System.err.println(" SEG_QUADTO ("+segmentCoordinatesArray[0]+", "+segmentCoordinatesArray[1]+"), ("+segmentCoordinatesArray[2]+", "+segmentCoordinatesArray[3]+"), ("+segmentCoordinatesArray[4]+", "+segmentCoordinatesArray[5]+")");
                    if (segmentCoordinatesArray[0] < UPPER_BND && segmentCoordinatesArray[0] > LOWER_BND &&
                            segmentCoordinatesArray[1] < UPPER_BND && segmentCoordinatesArray[1] > LOWER_BND &&
                            segmentCoordinatesArray[2] < UPPER_BND && segmentCoordinatesArray[2] > LOWER_BND &&
                            segmentCoordinatesArray[3] < UPPER_BND && segmentCoordinatesArray[3] > LOWER_BND &&
                            segmentCoordinatesArray[4] < UPPER_BND && segmentCoordinatesArray[4] > LOWER_BND &&
                            segmentCoordinatesArray[5] < UPPER_BND && segmentCoordinatesArray[5] > LOWER_BND) {
                        coordinates.put(segmentCoordinatesArray[0]);
                        coordinates.put(segmentCoordinatesArray[1]);
                        coordinates.put(segmentCoordinatesArray[2]);
                        coordinates.put(segmentCoordinatesArray[3]);
                        coordinates.put(segmentCoordinatesArray[4]);
                        coordinates.put(segmentCoordinatesArray[5]);
                    } else {
                        skip = true;
                    }
                    break;
                case PathIterator.SEG_CLOSE:
                    // System.err.println(" SEG_CLOSE");
                    break;
            }

            if (!skip) {
                types.put(type);
            }

            pi.next();
        }

        return pi.getWindingRule();
    }

    public synchronized void doLine(CRenderer renderer, SunGraphics2D sg2d, float x1, float y1, float x2, float y2) {
        // System.err.println("-- doLine x1="+x1+" y1="+y1+" x2="+x2+" y2="+y2+" paint="+sg2d.paint);
        setupGraphicsState(sg2d, kLine, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        renderer.doLine(this, x1, y1, x2, y2);
    }

    public synchronized void doRect(CRenderer renderer, SunGraphics2D sg2d, float x, float y, float width, float height, boolean isfill) {
        // System.err.println("-- doRect x="+x+" y="+y+" w="+width+" h="+height+" isfill="+isfill+" paint="+sg2d.paint);
        if ((isfill) && (isCustomPaint(sg2d))) {
            setupGraphicsState(sg2d, kRect, (int) x, (int) y, (int) width, (int) height);
        } else {
            setupGraphicsState(sg2d, kRect, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        }
        renderer.doRect(this, x, y, width, height, isfill);
    }

    public synchronized void doRoundRect(CRenderer renderer, SunGraphics2D sg2d, float x, float y, float width, float height, float arcW, float arcH, boolean isfill) {
        // System.err.println("--- doRoundRect");
        if ((isfill) && (isCustomPaint(sg2d))) {
            setupGraphicsState(sg2d, kRoundRect, (int) x, (int) y, (int) width, (int) height);
        } else {
            setupGraphicsState(sg2d, kRoundRect, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        }
        renderer.doRoundRect(this, x, y, width, height, arcW, arcH, isfill);
    }

    public synchronized void doOval(CRenderer renderer, SunGraphics2D sg2d, float x, float y, float width, float height, boolean isfill) {
        // System.err.println("--- doOval");
        if ((isfill) && (isCustomPaint(sg2d))) {
            setupGraphicsState(sg2d, kOval, (int) x, (int) y, (int) width, (int) height);
        } else {
            setupGraphicsState(sg2d, kOval, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        }
        renderer.doOval(this, x, y, width, height, isfill);
    }

    public synchronized void doArc(CRenderer renderer, SunGraphics2D sg2d, float x, float y, float width, float height, float startAngle, float arcAngle, int type, boolean isfill) {
        // System.err.println("--- doArc");
        if ((isfill) && (isCustomPaint(sg2d))) {
            setupGraphicsState(sg2d, kArc, (int) x, (int) y, (int) width, (int) height);
        } else {
            setupGraphicsState(sg2d, kArc, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        }

        renderer.doArc(this, x, y, width, height, startAngle, arcAngle, type, isfill);
    }

    public synchronized void doPolygon(CRenderer renderer, SunGraphics2D sg2d, int[] xpoints, int[] ypoints, int npoints, boolean ispolygon, boolean isfill) {
        // System.err.println("--- doPolygon");

        if ((isfill) && (isCustomPaint(sg2d))) {
            int minx = xpoints[0];
            int miny = ypoints[0];
            int maxx = minx;
            int maxy = miny;
            for (int i = 1; i < npoints; i++) {
                int x = xpoints[i];
                if (x < minx) {
                    minx = x;
                } else if (x > maxx) {
                    maxx = x;
                }

                int y = ypoints[i];
                if (y < miny) {
                    miny = y;
                } else if (y > maxy) {
                    maxy = y;
                }
            }
            setupGraphicsState(sg2d, kPolygon, minx, miny, maxx - minx, maxy - miny);
        } else {
            setupGraphicsState(sg2d, kPolygon, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        }
        renderer.doPoly(this, xpoints, ypoints, npoints, ispolygon, isfill);
    }

    FloatBuffer shapeCoordinatesArray = null;
    IntBuffer shapeTypesArray = null;

    public synchronized void drawfillShape(CRenderer renderer, SunGraphics2D sg2d, GeneralPath gp, boolean isfill, boolean shouldApplyOffset) {
        // System.err.println("--- drawfillShape");

        if ((isfill) && (isCustomPaint(sg2d))) {
            Rectangle bounds = gp.getBounds();
            setupGraphicsState(sg2d, kShape, bounds.x, bounds.y, bounds.width, bounds.height);
        } else {
            setupGraphicsState(sg2d, kShape, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        }

        int shapeLength = getPathLength(gp);

        if ((shapeCoordinatesArray == null) || (shapeCoordinatesArray.capacity() < (shapeLength * 6))) {
            shapeCoordinatesArray = getBufferOfSize(shapeLength * 6).asFloatBuffer(); // segment can have a max of 6
                                                                                      // coordinates
        }
        if ((shapeTypesArray == null) || (shapeTypesArray.capacity() < shapeLength)) {
            shapeTypesArray = getBufferOfSize(shapeLength).asIntBuffer();
        }

        int windingRule = getPathCoordinates(gp, shapeCoordinatesArray, shapeTypesArray);

        renderer.doShape(this, shapeLength, shapeCoordinatesArray, shapeTypesArray, windingRule, isfill, shouldApplyOffset);
    }

    public synchronized void blitImage(CRenderer renderer, SunGraphics2D sg2d, SurfaceData img, boolean fliph, boolean flipv, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, Color bgColor) {
        // System.err.println("--- blitImage sx="+sx+", sy="+sy+", sw="+sw+", sh="+sh+", img="+img);
        OSXOffScreenSurfaceData osxsd = (OSXOffScreenSurfaceData) img;
        synchronized (osxsd.getLockObject()) {
            int w = osxsd.bim.getWidth();
            int h = osxsd.bim.getHeight();

            // the image itself can have outstanding graphics primitives that might need to be flushed
            setupGraphicsState(sg2d, kImage, sg2d.font, 0, 0, fBounds.width, fBounds.height);

            // 04/06/04 cmc: radr://3612381 Graphics.drawImage ignores bgcolor parameter
            if (bgColor != null) {
                img = osxsd.getCopyWithBgColor(bgColor);
            }

            renderer.doImage(this, img, fliph, flipv, w, h, sx, sy, sw, sh, dx, dy, dw, dh);
        }
    }

    public interface CGContextDrawable {
        public void drawIntoCGContext(final long cgContext);
    }

    public synchronized void drawString(CTextPipe renderer, SunGraphics2D sg2d, long nativeStrikePtr, String str, double x, double y) {
        // System.err.println("--- drawString str=\""+str+"\"");
        // see <rdar://problem/3825795>. We don't want to call anything if the string is empty!
        if (str.length() == 0) { return; }

        setupGraphicsState(sg2d, kString, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        renderer.doDrawString(this, nativeStrikePtr, str, x, y);
    }

    public synchronized void drawGlyphs(CTextPipe renderer, SunGraphics2D sg2d, long nativeStrikePtr, GlyphVector gv, float x, float y) {
        // System.err.println("--- drawGlyphs");
        setupGraphicsState(sg2d, kGlyphs, gv.getFont(), 0, 0, fBounds.width, fBounds.height);
        renderer.doDrawGlyphs(this, nativeStrikePtr, gv, x, y);
    }

    public synchronized void drawUnicodes(CTextPipe renderer, SunGraphics2D sg2d, long nativeStrikePtr, char[] unicodes, int offset, int length, float x, float y) {
        // System.err.println("--- drawUnicodes "+(new String(unicodes, offset, length)));
        setupGraphicsState(sg2d, kUnicodes, sg2d.font, 0, 0, fBounds.width, fBounds.height);
        if (length == 1) {
            renderer.doOneUnicode(this, nativeStrikePtr, unicodes[offset], x, y);
        } else {
            renderer.doUnicodes(this, nativeStrikePtr, unicodes, offset, length, x, y);
        }
    }

    // used by copyArea:

    Rectangle srcCopyAreaRect = new Rectangle();
    Rectangle dstCopyAreaRect = new Rectangle();
    Rectangle finalCopyAreaRect = new Rectangle();
    Rectangle copyAreaBounds = new Rectangle();

    void intersection(Rectangle r1, Rectangle r2, Rectangle r3) {
        // this code is taken from Rectangle.java (modified to put results in r3)
        int tx1 = r1.x;
        int ty1 = r1.y;
        long tx2 = tx1 + r1.width;
        long ty2 = ty1 + r1.height;

        int rx1 = r2.x;
        int ry1 = r2.y;
        long rx2 = rx1 + r2.width;
        long ry2 = ry1 + r2.height;

        if (tx1 < rx1) tx1 = rx1;
        if (ty1 < ry1) ty1 = ry1;
        if (tx2 > rx2) tx2 = rx2;
        if (ty2 > ry2) ty2 = ry2;

        tx2 -= tx1;
        ty2 -= ty1;

        // tx2,ty2 will never overflow (they will never be
        // larger than the smallest of the two source w,h)
        // they might underflow, though...
        if (tx2 < Integer.MIN_VALUE) tx2 = Integer.MIN_VALUE;
        if (ty2 < Integer.MIN_VALUE) ty2 = Integer.MIN_VALUE;

        r3.setBounds(tx1, ty1, (int) tx2, (int) ty2);
    }

    /**
     * Clips the copy area to the heavyweight bounds and returns the clipped rectangle.
     * The returned clipped rectangle is in the coordinate space of the surface.
     */
    protected Rectangle clipCopyArea(SunGraphics2D sg2d, int x, int y, int w, int h, int dx, int dy) {
        // we need to clip against the heavyweight bounds
        copyAreaBounds.setBounds(sg2d.devClip.getLoX(), sg2d.devClip.getLoY(), sg2d.devClip.getWidth(), sg2d.devClip.getHeight());

        // clip src rect
        srcCopyAreaRect.setBounds(x, y, w, h);
        intersection(srcCopyAreaRect, copyAreaBounds, srcCopyAreaRect);
        if ((srcCopyAreaRect.width <= 0) || (srcCopyAreaRect.height <= 0)) {
            // src rect outside bounds
            return null;
        }

        // clip dst rect
        dstCopyAreaRect.setBounds(srcCopyAreaRect.x + dx, srcCopyAreaRect.y + dy, srcCopyAreaRect.width, srcCopyAreaRect.height);
        intersection(dstCopyAreaRect, copyAreaBounds, dstCopyAreaRect);
        if ((dstCopyAreaRect.width <= 0) || (dstCopyAreaRect.height <= 0)) {
            // dst rect outside clip
            return null;
        }

        x = dstCopyAreaRect.x - dx;
        y = dstCopyAreaRect.y - dy;
        w = dstCopyAreaRect.width;
        h = dstCopyAreaRect.height;

        finalCopyAreaRect.setBounds(x, y, w, h);

        return finalCopyAreaRect;
    }

    // <rdar://3785539> We only need to mark dirty on screen surfaces. This method is
    // marked as protected and it is intended for subclasses to override if they need to
    // be notified when the surface is dirtied. See CPeerSurfaceData.markDirty() for implementation.
    // We don't do anything for buffered images.
    protected void markDirty(boolean markAsDirty) {
        // do nothing by default
    }

    // LazyDrawing optimization implementation:

    @Override
    public boolean canRenderLCDText(SunGraphics2D sg2d) {
        if (sg2d.compositeState <= SunGraphics2D.COMP_ISCOPY &&
                sg2d.paintState <= SunGraphics2D.PAINT_ALPHACOLOR &&
                sg2d.clipState <= SunGraphics2D.CLIP_RECTANGULAR &&
                // sg2d.surfaceData.getTransparency() == Transparency.OPAQUE &&
                // This last test is a workaround until we fix loop selection
                // in the pipe validation
                sg2d.antialiasHint != SunHints.INTVAL_ANTIALIAS_ON) { return true; }
        return false; /* for now - in the future we may want to search */
    }

    public static boolean IsSimpleColor(Object c) {
        return ((c instanceof Color) || (c instanceof SystemColor) || (c instanceof javax.swing.plaf.ColorUIResource));
    }

    static {
        if ((kColorPointerIndex % 2) != 0) {
            System.err.println("kColorPointerIndex=" + kColorPointerIndex + " is NOT aligned for 64 bit");
            System.exit(0);
        }
    }
}
